///////////////////////////////////////////////////////////////////////////////
//    Copyright (c) 2021 CASTest Corporation Limited. All Rights Reserved    //
///////////////////////////////////////////////////////////////////////////////


#include "Netlist.h"

Netlist::Netlist() {
    InitNetlist();
}

Netlist::~Netlist() {
    for (int i = 0; i < _netlist.size(); i++) {
        if (_netlist[i] != nullptr) {
            delete _netlist[i];
        }
    }
    _netlist.clear();
    _pi.clear();
    _po.clear();
    _ppi.clear();
    _ppo.clear();
    _supply.clear();
    _stems.clear();
    _netnames.clear();
    _levels.clear();
    _cells.clear();
    _dff_cells.clear();
}

void Netlist::ParseNetlist(const string &primitive_vy_path, const string &cell_vy_path) {

    ReadPrimitiveVy(primitive_vy_path);
    ReadCellVy(cell_vy_path);
    BuildConnection();
    AddPO();
    BuildStem();
    SetCellBorder();
    Levelize();

    if (!_dff_gates.empty()) {
        SetDFFMasterMap();
    }
}

void Netlist::ParseBench(const string &primitive_vy_path) {
    ReadPrimitiveVy(primitive_vy_path);
    BuildConnection();
    AddPO();
    BuildStem();
    Levelize();
}

void Netlist::SetGlobalGate(int gate_id) {
    Gate *gptr = _netlist[gate_id];
    assert(gptr != nullptr);
    gptr->SetGlobal(true);
}

void Netlist::SetSpecialGate(int gate_id, SpecialGate gate_type) {
    Gate *gptr = _netlist[gate_id];
    assert(gptr != nullptr);
    gptr->SetSpecialGate(gate_type);
}

void Netlist::InitNetlist() {
    // primitive data
    _num_net = 0;
    _num_pi = 0;
    _num_po = 0;
    _max_level = 0;
    _netlist.clear();
    _pi.clear();
    _po.clear();
    _ppi.clear();
    _ppo.clear();
    _supply.clear();
    _stems.clear();
    _netnames.clear();
    _levels.clear();

    // cell data
    _cells.clear();
    _dff_cells.clear();
    _cells_size = 0;
    _cell_nets.clear();
    _cell_pins.clear();
    _cell_names.clear();
    _cell_id.clear();
}

void Netlist::ReadPrimitiveVy(const string &vy_file_path) {
    ifstream fin(vy_file_path);
    if (!fin.is_open()) {
        cerr << Errors::ErrorsMsg(ErrorType::FILE_NOT_FOUND, vy_file_path);
        exit(1);
    }
    vector<string> vstr;
    std::string line;
    while (fin.good()) {
        getline(fin, line);
        vstr.push_back(line);
    }
    // current gate's output net id
    int gate_idx = 0;
    Gate *gate = nullptr;

    // initial pi, inout, po
    _num_net = stoi(vstr[0]);
    _netlist.resize(3 * _num_net);
    _num_pi = stoi(vstr[1]);
    _num_po = stoi(vstr[5]);
    istringstream pi_str(vstr[2]), po_str(vstr[6]);
    while (pi_str >> gate_idx) {
        gate = new Gate(gate_idx, G_PI, "");
        _netlist[gate_idx] = gate;
        _pi.push_back(gate);
    }
    while (po_str >> gate_idx) {
        // po gate's logic id and name is defined in the following code
        if (_netlist[gate_idx] != nullptr) {
            _po.push_back(_netlist[gate_idx]);
        } else {
            gate = new Gate(gate_idx, 0, "");
            _netlist[gate_idx] = gate;
            _po.push_back(gate);
        }
    }
    string str;
    int count_flag = 0;
    // initial other logic gates
    for (int i = 7; i < vstr.size(); i++) {
        vector<string> vtmp;
        istringstream ifline(vstr[i]);
        while (ifline >> str) {
            vtmp.push_back(str);
        }
        if (vtmp.size() > 0 && vtmp.size() == 2) {
            int id = stoi(vtmp[0]);
            if (-1 == id) {
                count_flag++;
            }
            if ( 1 == count_flag) {
                int net_id = id;
                _netnames[net_id] = vtmp[1];
            } else if ( 2 == count_flag) {
                int pin_id = id;
                _pinnames[pin_id] = vtmp[1];
            }

        } else if (vtmp.size() > 2) {
            int logic_id = stoi(vtmp[0]);
            string name = vtmp[1];
            int num_out = stoi(vtmp[2]);
            gate_idx = stoi(vtmp[3]);
            int succ_pin_id = stoi(vtmp[4]);
            int num_inout = stoi(vtmp[5]);
            int num_in = stoi(vtmp[6]);
            int index = 6;
            // po gate
            if (_netlist[gate_idx] != nullptr) {
                Gate *po = _netlist[gate_idx];
                po->SetLogicType(logic_id);
                po->SetName(name);
                po->AddSuccPin(succ_pin_id);
                for (size_t i = 0; i < num_in; i++) {
                    int pred_net_id = stoi(vtmp[++index]);
                    int pred_pin_id = stoi(vtmp[++index]);
                    po->AddToPred(pred_net_id);
                    po->AddPredPin(pred_pin_id);
                }
            } else {
                // other gate
                gate = new Gate(gate_idx, logic_id, name);
                gate->AddSuccPin(succ_pin_id);
                for (size_t i = 0; i < num_in; i++) {
                    int pred_net_id = stoi(vtmp[++index]);
                    int pred_pin_id = stoi(vtmp[++index]);
                    gate->AddToPred(pred_net_id);
                    gate->AddPredPin(pred_pin_id);
                }
                if (gate->GetLogicType() == G_DFF) {
                    _dff_gates.push_back(gate);
                }
                if (gate->GetLogicType() == G_DLAT) {
                    _dlat_gates.push_back(gate);
                }
                _netlist[gate_idx] = gate;
            }
        }
    }
    // set pi name
    for (int i = 0; i < _pi.size(); i++) {
        Gate* gptr = _pi[i];
        assert(gptr != nullptr);
        int pi_idx = gptr->GetId();
        gptr->SetName(_netnames[pi_idx]);
    }
    fin.close();
}

void Netlist::BuildConnection() {
    vector<bool> visited(_num_net, false);
    map<int, bool> ste_in;
    bool supply0_flag = false, supply1_flag = false;

    for (int i = 0; i < _num_po; i++) {
        Gate *gate = _po[i];
        assert(gate != nullptr);
        queue<Gate *> gate_que;
        if (!visited[gate->GetId()]) {
            gate_que.push(gate);
            visited[gate->GetId()] = true;
        }

        while (!gate_que.empty()) {
            Gate *tmp = gate_que.front();
            gate_que.pop();
            vector<int> &pred = tmp->GetAllPred();
            for (int j = 0; j < pred.size(); j++) {
                // processing supply0 & supply1
                if (_netnames[pred[j]] == "supply0" && !supply0_flag) {
                    Gate *supply0_gate = new Gate(pred[j], G_SUPPLY0, "supply0");
                    supply0_gate->SetValue(LOGIC_ZERO); // atpg value
                    _netlist[pred[j]] = supply0_gate;
                    _supply.push_back(supply0_gate);
                    // SetGlobalGate(supply0_gate->GetId());
                    SetSpecialGate(supply0_gate->GetId(), SUPPLY_GATE);
                    supply0_flag = true;
                }
                if (_netnames[pred[j]] == "supply1" && !supply1_flag) {
                    Gate *supply1_gate = new Gate(pred[j], G_SUPPLY1, "supply1");
                    supply1_gate->SetValue(LOGIC_ONE); // atpg value
                    _netlist[pred[j]] = supply1_gate;
                    _supply.push_back(supply1_gate);
                    // SetGlobalGate(supply1_gate->GetId());
                    SetSpecialGate(supply1_gate->GetId(), SUPPLY_GATE);
                    supply1_flag = true;
                }
                // connection fanin & fanout
                Gate *fanin = _netlist[pred[j]];
                tmp->AddToFanins(fanin);
                fanin->AddToFanouts(tmp);
                if (!visited[fanin->GetId()]) {
                    gate_que.push(fanin);
                    visited[fanin->GetId()] = true;
                }
            }
        }
    }
}

void Netlist::AddPO() {
    Gate *gptr = nullptr;
    for (int i = 0; i < _po.size(); i++) {
        gptr = _po[i];
        assert(gptr != nullptr);
        _num_net += 1;
        int id = _num_net - 2;
        int po_idx = gptr->GetId();
        Gate *po = new Gate(id, G_PO, _netnames[po_idx]);
        _netlist[id] = po;
        gptr->AddToFanouts(po);
        po->AddToFanins(gptr);
        po->AddToPred(gptr->GetId());
        _po[i] = po;
    }
}

void Netlist::BuildStem() {
    Gate *stem = nullptr;
    Gate *branch = nullptr;
    Gate *new_gate = nullptr;
    _stems.clear();
    for (int i = 0; i < _num_net; i++) {
        Gate *stem = _netlist[i];
        if (stem != nullptr) {
            if (stem->GetFanoutsSize() > 1 && !stem->IsGlobal()) {
                stem->SetStem(true);
                SetStems(stem);
            }
        }
    }
    for (int m = 0; m < _stems.size(); m++) {
        stem = _stems[m];
        assert(stem != nullptr);
        for (int i = 0; i < stem->GetFanoutsSize(); i++) {
            branch = stem->GetFanout(i);
            _num_net += 1;
            int id = _num_net - 2;
            new_gate = new Gate(id, G_STEM, "STEM_" + to_string(id));
            _netlist[id] = new_gate;
            new_gate->AddToFanins(stem);
            new_gate->AddToFanouts(branch);
            new_gate->AddToPred(stem->GetId());
            stem->ChangeFanout(i, new_gate);

            for (int j = 0; j < branch->GetFaninsSize(); j++) {
                Gate *cur_fanin = branch->GetFanin(j);
                if (cur_fanin->GetId() == stem->GetId()) {
                    branch->SetPred(j, new_gate->GetId());
                    branch->ChangeFanin(j, new_gate);
                    break;
                }
            }
        }
    }
}

void Netlist::SetCellBorder() {
    for (int i = 0; i < _cells_size; i++) {
        Cell *cptr = _cells[i];
        assert(cptr != nullptr);
        //    if (cptr->get_logic_id() >= G_BASIC) {
        auto inputs  = cptr->GetInputs();
        auto outputs = cptr->GetOutputs();
        for (auto &elem : inputs) {
            int border_net_id = elem.second;
            Gate *gptr = _netlist[border_net_id];
            if (gptr == nullptr)
                continue;
            gptr->SetCellBorder(true);
            if (gptr->IsStem()) {
                for (int i = 0; i < gptr->GetFanoutsSize(); i++) {
                    gptr->GetFanout(i)->SetCellBorder(true);
                }
            }
        }
        for (auto &elem : outputs) {
            int border_net_id = elem.second;
            Gate *gptr = _netlist[border_net_id];
            if (gptr == nullptr)
                continue;
            gptr->SetCellBorder(true);
            if (gptr->IsStem()) {
                for (int i = 0; i < gptr->GetFanoutsSize(); i++) {
                    gptr->GetFanout(i)->SetCellBorder(true);
                }
            }
        }
        //    }
    }
}

void Netlist::SetDPI() {
    queue<Gate *> q_gates;
    Gate *gptr = nullptr;
    Gate *out = nullptr;
    vector<int> fanin_count(_num_net, 0);
    // set pi and its fanout level
    for (int i = 0; i < _pi.size(); i++) {
        gptr = _pi[i];
        gptr->SetDpi(0);
        for (int j = 0; j < gptr->GetFanoutsSize(); j++) {
            out = gptr->GetFanout(j);
            fanin_count[out->GetId()]++;
            if (fanin_count[out->GetId()] == out->GetFaninsSize()) {
                out->SetDpi(1);
                if(out->GetLogicType() != G_DFF){
                    q_gates.push(out);
                }
            }
        }
    }

    // set supply0 and supply1 gate level
    for (int i = 0; i < _supply.size(); i++) {
        gptr = _supply[i];
        gptr->SetDpi(0);
        for (int j = 0; j < gptr->GetFanoutsSize(); j++) {
            out = gptr->GetFanout(j);
            fanin_count[out->GetId()]++;
            if (fanin_count[out->GetId()] == out->GetFaninsSize()) {
                out->SetDpi(1);
                if(out->GetLogicType() != G_DFF) {
                    q_gates.push(out);
                }
            }
        }
    }

    // process dff output as PPI
    for(auto dff : _dff_gates) {
        for (auto fanout : dff->GetAllFanouts()) {
            fanin_count[fanout->GetId()]++;
            if (fanin_count[fanout->GetId()] == fanout->GetFaninsSize()) {
                fanout->SetDpi(1);
                if(fanout->GetLogicType() != G_DFF) {
                    q_gates.push(fanout);
                }
            }
        }
    }

    // levelize all other gates
    int l1 = 0, l2 = 0;
    while (!q_gates.empty()) {
        gptr = q_gates.front();
        q_gates.pop();
        l2 = gptr->GetDpi();
        // update _max_level
        if (l2 > _max_level) {
            _max_level = l2;
        }
        for (int i = 0; i < gptr->GetFanoutsSize(); i++) {
            out = gptr->GetFanout(i);
            l1 = out->GetDpi();
            if (l1 <= l2) {
                out->SetDpi(l2 + 1);
                if(l2+1> _max_level) {
                    _max_level = l2 + 1;
                }
            }
            fanin_count[out->GetId()]++;
            if (fanin_count[out->GetId()] == out->GetFaninsSize()) {
                if(out->GetLogicType() != G_DFF) {
                    q_gates.push(out);
                }
            }
        }
    } // while

    fanin_count.clear();

#ifdef DEBUG
    for(int i =0 ; i<_num_net; i++) {
        auto gptr = _netlist[i];
        if (gptr != nullptr) {
            if(gptr->GetDpi() < 0) {
                cout<< gptr->GetName() << " : " << gptr->GetDpi()<<endl;
            }
        }
    }
#endif
}

void Netlist::SetLevelArray() {
    // initialize _levels
    _levels.resize(_max_level + 1);
    for (int i = 0; i < _num_net; i++) {
        Gate *gptr = _netlist[i];
        if (gptr != nullptr && gptr->GetDpi() >= 0) {
            _levels[gptr->GetDpi()].push_back(gptr);
        }
    }
}

void Netlist::SetDPO() {
    // from po to pi
    Gate *gptr = nullptr;
    int depth = 0;
    for (int i = 0; i < _po.size(); i++) {
        gptr = _po[i];
        assert(gptr != nullptr);
        gptr->SetDpo(0);
    }

    for (int i = _max_level; i >= 0; i--) {
        for (int j = 0; j < _levels[i].size(); j++) {
            gptr = _levels[i][j];
            assert(gptr != nullptr);
            depth = -1;
            for (int k = 0; k < gptr->GetFanoutsSize(); k++) {
                depth = std::min(depth, gptr->GetFanout(k)->GetDpo());
            }
            depth += 1;
            gptr->SetDpo(depth);
        }
    }
}

void Netlist::Levelize() {
    SetDPI();
    SetLevelArray();
    SetDPO();
}

void Netlist::SetDFFMasterMap() {
    for (size_t i = 0; i < _dff_gates.size(); i++) {
        int dff_gate_id = _dff_gates[i]->GetId();
        _dff_gate_id_to_master_map[dff_gate_id] = i;
    }
}

void Netlist::PrintNetlistInfo(ostream &out) {

    out << "Num Nets: " << _num_net << endl;
    out << "PI Gates:" << endl;
    for ( int i = 0; i < _pi.size(); i++ ) {
        // _pi[i]->print_gate_info(out);
        out << *_pi[i];
        out << " | net name:" << _netnames[_pi[i]->GetId()] <<endl;
    }

    out << "PPI Gates:" << endl;
    for ( int i = 0; i < _ppi.size(); i++ ) {
        // _ppi[i]->print_gate_info(out);
        out << *_ppi[i];
        out << " | net name:" << _netnames[_ppi[i]->GetId()] <<endl;
    }

    out << "All Circuit Gates:" << endl;
    for ( int i = 0; i < _num_net; i++ ) {
        if ( _netlist[i] != nullptr ) {
            // _netlist[i]->print_gate_info(out);
            out << *_netlist[i];
            out << " | net name:" << _netnames[_netlist[i]->GetId()] <<endl;
        }
    }
    out << "PO Gates:" << endl;
    for ( int i = 0; i < _po.size(); i++ ) {
        // _po[i]->print_gate_info(out);
        out << *_po[i];
        out << " | net name:" << _netnames[_po[i]->GetId()] <<endl;
    }

    out << "PPO Gates:" << endl;
    for ( int i = 0; i < _ppo.size(); i++ ) {
        // _ppo[i]->print_gate_info(out);
        out << *_ppo[i];
        out << " | net name:" << _netnames[_ppo[i]->GetId()] <<endl;
    }

    out << "Stem Gates:" << endl;
    for ( auto it : _stems ) {
        // (*it).print_gate_info(out);
        out << (*it);
        out << " | net name:" << _netnames[it->GetId()] <<endl;
    }
}

void Netlist::PrintGateFromPI(ostream &out) {
    for (int i = 0; i < _num_pi; i++) {
        Gate *gate = (Gate *)_pi[i];
        queue<Gate *> gate_que;
        gate_que.push(gate);
        while (!gate_que.empty()) {
            int size = gate_que.size();
            for (int i = 0; i < size; i++) {
                Gate *tmp = gate_que.front();
                gate_que.pop();
                out << tmp->GetId() << " " << tmp->GetName() << "    |   ";
                auto fanouts = tmp->GetAllFanouts();
                for (int j = 0; j < fanouts.size(); j++) {
                    gate_que.push(fanouts[j]);
                }
            }
            out << endl;
        }
        out << endl;
    }
}

void Netlist::PrintGateFromPO(ostream &out) {
    for (int i = 0; i < _num_po; i++) {
        Gate *gate = _po[i];
        queue<Gate *> gate_que;
        gate_que.push(gate);
        while (!gate_que.empty()) {
            int size = gate_que.size();
            for (int i = 0; i < size; i++) {
                Gate *tmp = gate_que.front();
                gate_que.pop();
                out << tmp->GetId() << " " << tmp->GetName() << "    |   ";
                auto fanins = tmp->GetAllFanins();
                for (int j = 0; j < fanins.size(); j++) {
                    gate_que.push(fanins[j]);
                }
            }
            out << endl;
        }
        out << endl;
    }
}

void Netlist::PrintLevelArray(ostream &out) {
    Gate *gptr;
    for (int i = 0; i <= _max_level; i++) {
        int size = _levels[i].size();
        out << "level: " << i << " -> ";
        for (int j = 0; j < size; j++) {
            gptr = _levels[i][j];
            out << "(id:" << gptr->GetId() << ", dpi:" << gptr->GetDpi() << ", dpo:" << gptr->GetDpo() << ") ";
        }
        out << endl;
    }
}

void Netlist::PrintTestability(ostream &out) {
    Gate *gptr;
    for (int i = 0; i <= _max_level; i++) {
        int size = _levels[i].size();
        out << "level: " << i << " -> ";
        for (int j = 0; j < size; j++) {
            gptr = _levels[i][j];
            out << "[" << gptr->GetC0() << "," << gptr->GetC1() << "," << gptr->GetOb() << "] ";
        }
        out << endl;
    }
}

string Netlist::ConvertGateValue(Gate *gptr) {
    string val;
    switch (gptr->GetValue()) {
    case LOGIC_ONE:
        val = "1";
        break;
    case LOGIC_ZERO:
        val = "0";
        break;
    case LOGIC_D:
        val = "D";
        break;
    case LOGIC_DB:
        val = "DB";
        break;
    case LOGIC_X:
        val = "X";
        break;
    default:
        string msg = "not support logic value type";
        cerr << Errors::ErrorsMsg(ErrorType::INVALID, msg) << endl;
        exit(1);
    }
    return val;
}

void Netlist::PrintGateVal(ostream &out) {
    Gate *gptr = nullptr;
    for (int i = 0; i <= _max_level; i++) {
        out << "level:" << i << " -> ";
        for (int j = 0; j < _levels[i].size(); j++) {
            gptr = _levels[i][j];
            out << gptr->GetId() << "(" << ConvertGateValue(gptr) << ") ";
        }
        out << endl;
    }
    cout << endl;
}

//////////////////////////////////////////////////
// cell
void Netlist::ReadCellVy(const string &vy_file_path) {
    ifstream fin(vy_file_path);
    if (!fin.is_open()) {
        cerr <<Errors::ErrorsMsg(ErrorType::FILE_NOT_FOUND, vy_file_path) << endl;
        exit(1);
    }
    vector<string> vstr;
    std::string line;
    while (fin.good()) {
        getline(fin, line);
        vstr.push_back(line);
    }
    int count = 0;
    string str;
    for (int i = 7; i < vstr.size(); i++) {
        vector<string> elems;
        istringstream ifline(vstr[i]);
        while (ifline >> str) {
            elems.push_back(str);
        }
        if (elems.size() > 2) {
            int m = 2; // out id start
            int cell_logic_id = stoi(elems[0]);
            string inst_name = elems[1];
            Cell *cell = new Cell(_cells_size, cell_logic_id, inst_name);
            // processing buf_assign in cell.vy file .
            if (cell_logic_id < 200) {
                int num_out = stoi(elems[m]);
                for (int i = 0; i < num_out; ++i) {
                    int net_id = stoi(elems[++m]);
                    int pin_id = stoi(elems[++m]);
                    cell->AddOutputs(pin_id, net_id);
                }
                int num_inout = stoi(elems[++m]);
                for (int i = 0; i < num_inout; i++) {
                    int net_id = stoi(elems[++m]);
                    int pin_id = stoi(elems[++m]);
                    cell->AddInouts(pin_id, net_id);
                }
                int num_in = stoi(elems[++m]);
                for (int i = 0; i < num_in; i++) {
                    int net_id = stoi(elems[++m]);
                    int pin_id = stoi(elems[++m]);
                    cell->AddInputs(pin_id, net_id);
                }
            } else {
                int num_out = stoi(elems[m]);
                for (int i = 0; i < num_out; i++) {
                    int net_id = stoi(elems[++m]);
                    int pin_id = stoi(elems[++m]);
                    // _netlist[num1]->SetCellBorder(true);
                    cell->AddOutputs(pin_id, net_id);
                }
                int num_inout = stoi(elems[++m]);
                for (int i = 0; i < num_inout; i++) {
                    int net_id = stoi(elems[++m]);
                    int pin_id = stoi(elems[++m]);
                    cell->AddInouts(pin_id, net_id);
                }
                int num_in = stoi(elems[++m]);
                for (int i = 0; i < num_in; i++) {
                    int net_id = stoi(elems[++m]);
                    int pin_id = stoi(elems[++m]);
                    cell->AddInputs(pin_id, net_id);
                }
            }
            _cells.push_back(cell);
            _cell_id[inst_name] = _cells_size;
            _cells_size++;
        } else if (elems.size() > 0 && elems.size() == 2) {
            int id = stoi(elems[0]);
            string name = elems[1];
            if (count == 1) {
                SetCellNets(id, name);
            } else if (count == 2) {
                SetCellPins(id, name);
            } else if (count == 3) {
                SetCellNames(id, name);
            }
        } else {
            count++;
        }
    }

    fin.close();
}

string Netlist::GetCellInstName(string instance_name) {
    vector<string> ret = Utils::split(instance_name, "==");
    string str, res;
    for (int i = 1; i < ret.size() - 1; i++) {
        str += "==" + ret[i];
        if (_cell_id.count(str)) {
            res = str;
            break;
        }
    }
    return res;
}

Cell *Netlist::GetCell(string instance_name) {
    std::string token;
    string pin_name;
    //int count = 0;
    vector<string> ret;
    std::istringstream ss(instance_name);
    while (std::getline(ss, token, '=')) {
        if (!token.empty()) {
            ret.push_back(token);
        }
    }
    string str;
    // from 1 in split
    for (int i = 0; i < ret.size(); i++) {
        str += "==" + ret[i];
        if (_cell_id.count(str)) {
            int cell_id = _cell_id[str];
            return _cells[cell_id];
        }
    }
    return nullptr;
}

void Netlist::PrintCellInfo(ostream &out) {
    Gate *gptr = nullptr;
    // print pi
    out << "pi: ";
    for (int i = 0; i < _pi.size(); i++) {
        gptr = _pi[i];
        out << _netnames[gptr->GetId()] << " ";
    }
    out << endl;
    // print po
    out << "po: ";
    for (int i = 0; i < _po.size(); i++) {
        gptr = _po[i];
        unordered_map<int, bool> visited;
        Gate *fanin = gptr->GetFanin(0);
        if (fanin->GetLogicType() == G_STEM) {
            fanin = fanin->GetFanin(0);
        }
        if (!visited[fanin->GetId()]) {
            out << _netnames[fanin->GetId()] << " ";
            visited[fanin->GetId()] = true;
        }
    }
    out << endl;
    // print cell
    for (int i = 0; i < _cells_size; i++){
        Cell *cptr = _cells[i];
        assert(cptr != nullptr);
        unordered_map<int, int> inputs  = cptr->GetInputs();
        unordered_map<int, int> outputs = cptr->GetOutputs();
        if (cptr->GetLogicType() >= G_BASIC) {
            out << cptr->GetInstName() << ": ";
            for (auto elem : outputs) {
                out << _cell_pins[elem.second] << "(" << _cell_nets[elem.first] << ") ";
            }
            out << ":";
            for (auto elem : inputs) {
                out << _cell_pins[elem.second] << "(" << _cell_nets[elem.first] << ") ";
            }
            out << endl;
        } else {
            out << cptr->GetInstName() << " ";
            for (auto elem : outputs) {
                out << _cell_nets[elem.first] << " ";
            }
            out << ":";
            for (auto elem : inputs) {
                out << _cell_nets[elem.first] << " ";
            }
            out << endl;
        }
    }
}

void Netlist::TracePrimitiveScanChain(vector<int> test_so_ids,
                                      bool is_mux_input_a) {

    for (auto test_so_id : test_so_ids) {
        Gate *test_so_gptr = _netlist[test_so_id];
        vector<Gate *> scan_chain;
        queue<Gate *>  gate_queue;
        gate_queue.push(test_so_gptr);
        set<Gate *> visited;
        while (!gate_queue.empty()) {
            Gate *gptr = gate_queue.front();
            gate_queue.pop();

            Gate *fanin;
            if (gptr->GetFaninsSize() == 0) {
                continue;
            } else if (gptr->GetFaninsSize() == 1) {
                fanin = gptr->GetFanin(0);
            } else if (gptr->GetLogicType() == G_DFF) {
                fanin = gptr->GetFanin(3);
                scan_chain.push_back(gptr);
            } else if (gptr->GetLogicType() == G_MUX) {
                int input_index = is_mux_input_a ? 1 : 2;
                fanin = gptr->GetFanin(input_index);
            } else {
                string msg = to_string(gptr->GetId()) + ": gate has multiple inputs and is not mux or dff";
                cerr << Errors::ErrorsMsg(ErrorType::INVALID, msg) << endl;
                exit(1);
            }
            if (visited.find(fanin) == visited.end()) {
                gate_queue.push(fanin);
                visited.insert(fanin);
            }
        }
        // reverse(scan_chain.begin(),scan_chain.end());

        _scan_chains.push_back(scan_chain);
    }
}
